/*           Copyright(c) 2007-2008 Dave Sexton            *
 *                   XmlTreeView Control                   *
 * http://www.codeplex.com/DocProject/Project/License.aspx *
 ***********************************************************/
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using System.Runtime.Serialization;
using System.Xml.XPath;

namespace DaveSexton.Controls
{
	[Serializable]
	public class XmlTreeNode : TreeNode
	{
		#region Public Properties
		public IXPathNavigable DataSource
		{
			get
			{
				return source;
			}
			set
			{
				if (value == null)
					throw new ArgumentNullException("value");

				if (source == null || !source.CreateNavigator().IsSamePosition(value.CreateNavigator()))
				{
					source = value;

					OnDataSourceChanged(EventArgs.Empty);

					XmlTreeView tree = TreeView as XmlTreeView;

					if (tree != null)
						tree.NotifyNodeDataSourceChanged(this);
				}
			}
		}

		public new bool Checked
		{
			get
			{
				return base.Checked;
			}
			set
			{
				if (base.Checked == value)
					return;

				base.Checked = value;
				OnCheckedChanged(EventArgs.Empty);
			}
		}

		public string Id
		{
			get
			{
				if (id != null)
					return id;

				XmlTreeView tree = TreeView as XmlTreeView;

				if (tree == null)
					return id = Guid.NewGuid().ToString();
				else
				{
					id = (tree.nodeIndex.Count).ToString(System.Globalization.CultureInfo.CurrentCulture);

					tree.nodeIndex.Add(id, this);

					return id;
				}
			}
		}
		#endregion

		#region Private / Protected
		private IXPathNavigable source;
		private string id;
		#endregion

		#region Constructors
		/// <summary>
		/// Constructs a new instance of the <see cref="XmlTreeNode" /> class.
		/// </summary>
		public XmlTreeNode(IXPathNavigable dataSource)
		{
			if (dataSource == null)
				throw new ArgumentNullException("dataSource");

			source = dataSource;
		}

		/// <summary>
		/// Constructs a new instance of the <see cref="XmlTreeNode" /> class by copying the values of the specified <paramref name="clone"/>.
		/// </summary>
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
		protected XmlTreeNode(XmlTreeNode clone)
		{
			if (clone == null)
				throw new ArgumentNullException("clone");

			source = clone.source;

			BackColor = clone.BackColor;
			Checked = clone.Checked;
			ContextMenuStrip = clone.ContextMenuStrip;
			ForeColor = clone.ForeColor;
			ImageIndex = clone.ImageIndex;
			ImageKey = clone.ImageKey;
			Name = clone.Name;
			SelectedImageIndex = clone.SelectedImageIndex;
			SelectedImageKey = clone.SelectedImageKey;
			StateImageIndex = clone.StateImageIndex;
			StateImageKey = clone.StateImageKey;
			Tag = clone.Tag;
			Text = clone.Text;
			ToolTipText = clone.ToolTipText;

			if (clone.NodeFont != null)
				NodeFont = (System.Drawing.Font) clone.NodeFont.Clone();
		}

		/// <summary>
		/// Constructs a new instance of the <see cref="XmlTreeNode" /> class for deserialization.
		/// </summary>
		protected XmlTreeNode(SerializationInfo info, StreamingContext context)
			: base(info, context)
		{
		}
		#endregion

		#region Methods
		#region Iteration and Navigation
		public TNode FindNode<TNode>(Predicate<TNode> predicate)
			where TNode : XmlTreeNode
		{
			return XmlTreeView.FindNodeRecursive(Nodes, predicate);
		}

		public IEnumerable<TNode> FindNodes<TNode>(Predicate<TNode> predicate)
			where TNode : XmlTreeNode
		{
			return XmlTreeView.FindNodesRecursive(Nodes, predicate);
		}

		public void ForEachNode<TNode>(Action<TNode> action)
			where TNode : XmlTreeNode
		{
			XmlTreeView.ForEachNodeRecursive(Nodes, action);
		}
		#endregion

		public string GetAttribute(string name)
		{
			return XmlTreeViewDataManager.GetAttribute(source, name);
		}

		public void SetAttribute(string name, string value)
		{
			XmlTreeViewDataManager.SetAttribute(source, name, value);
		}

		public override object Clone()
		{
			return new XmlTreeNode(this);
		}
		#endregion

		#region Events
		// NOTE: Keep this field static to save memory.
		private static readonly object CheckedChangedEventLock = new object();
		private EventHandler CheckedChangedEvent;
		private readonly object DataSourceChangedEventLock = new object();
		private EventHandler DataSourceChangedEvent;

		/// <summary>
		/// Event raised when the <see cref="DataSource"/> property changes.
		/// </summary>
		public event EventHandler DataSourceChanged
		{
			add
			{
				lock (DataSourceChangedEventLock)
				{
					DataSourceChangedEvent += value;
				}
			}
			remove
			{
				lock (DataSourceChangedEventLock)
				{
					DataSourceChangedEvent -= value;
				}
			}
		}

		/// <summary>
		/// Raises the <see cref="DataSourceChanged" /> event.
		/// </summary>
		/// <param name="e"><see cref="EventArgs" /> object that provides the arguments for the event.</param>
		protected virtual void OnDataSourceChanged(EventArgs e)
		{
			EventHandler handler = null;

			lock (DataSourceChangedEventLock)
			{
				handler = DataSourceChangedEvent;
			}

			if (handler != null)
				handler(this, e);
		}

		/// <summary>
		/// Event raised when the <see cref="Checked"/> property value has changed.
		/// </summary>
		public event EventHandler CheckedChanged
		{
			add
			{
				lock (CheckedChangedEventLock)
				{
					CheckedChangedEvent += value;
				}
			}
			remove
			{
				lock (CheckedChangedEventLock)
				{
					CheckedChangedEvent -= value;
				}
			}
		}

		/// <summary>
		/// Raises the <see cref="CheckedChanged" /> event.
		/// </summary>
		/// <param name="e"><see cref="EventArgs" /> object that provides the arguments for the event.</param>
		protected internal virtual void OnCheckedChanged(EventArgs e)
		{
			EventHandler handler = null;

			lock (CheckedChangedEventLock)
			{
				handler = CheckedChangedEvent;
			}

			if (handler != null)
				handler(this, e);
		}
		#endregion
	}
}
